'use strict';

var utils = require('./../utils');
var buildURL = require('../helpers/buildURL');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');
var mergeConfig = require('./mergeConfig');
var validator = require('../helpers/validator');
// 检验版本的对象
var validators = validator.validators;
/**
 * Create a new instance of Axios
 *
 * @param {Object} instanceConfig The default config for the instance
 */
// Axios的处理流程：先执行请求拦截器，
// 然后执行transfromDataResquest,xhr,transfromDataRespones(这三部分在dispatchRequest这个流程中执行)
// 最后执行响应拦截器
function Axios(instanceConfig) {
    this.defaults = instanceConfig;
    this.interceptors = {
        request: new InterceptorManager(),
        response: new InterceptorManager()
    };
}

/**
 * Dispatch a request
 *
 * @param {Object} config The config specific for this request (merged with this.defaults)
 */
Axios.prototype.request = function request(config) {
    /*eslint no-param-reassign:0*/
    // Allow for axios('example/url'[, config]) a la fetch API
    // 对传入配置的处理
    // 这里可以处理不同的请求形式：axios('url',config)或者axios(config)
    if (typeof config === 'string') {
        config = arguments[1] || {};
        config.url = arguments[0];
    } else {
        config = config || {};
    }
    // 完善并补充config配置（跟自己之前设置的default相比进行添加或覆盖,合并两个配置）
    config = mergeConfig(this.defaults, config);

    // Set config.method
    if (config.method) {
        // 自动将请求的方法转化为小写
        config.method = config.method.toLowerCase();
    } else if (this.defaults.method) {
        // 没有在配置中设置请求方法使用默认配置
        config.method = this.defaults.method.toLowerCase();
        // 没有设置请求方法且没有默认配置，默认使用get方法
    } else {
        config.method = 'get';
    }

    var transitional = config.transitional;
    // 进行版本检验，1.0.0版本后，transitional这个属性被移除
    if (transitional !== undefined) {
        validator.assertOptions(transitional, {
            silentJSONParsing: validators.transitional(validators.boolean, '1.0.0'),
            forcedJSONParsing: validators.transitional(validators.boolean, '1.0.0'),
            clarifyTimeoutError: validators.transitional(validators.boolean, '1.0.0')
        }, false);
    }

    // filter out skipped interceptors
    // 拦截器链
    var requestInterceptorChain = [];
    // 是否为同步函数
    var synchronousRequestInterceptors = true;
    this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
        if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
            return;
        }
        // 确保每个请求拦截器都是同步的，最终的拦截器才是同步的
        synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
        // 将每个请求拦截器的成功回调和失败回调绑定在执行链的最前端
        requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
    });

    var responseInterceptorChain = [];
    // 注意这里的foreach方法是拦截器原型链上的foreach方法
    // 这个方法内部会遍历所有拦截器
    this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
        // 将每个响应拦截器加入到执行链的最后端
        responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
    });

    var promise;
    // 如果拦截器需要异步处理
    if (!synchronousRequestInterceptors) {
        // undefined用于占位，保证回调函数两个一组可以成对处理
        // 这里的dispatchRequest是处理请求的核心代码
        //  在dispatchRequest中会检验是否取消请求，适配器处理，并利用transfrom在发送请求前对数据进行处理
        var chain = [dispatchRequest, undefined];
        // 将整个请求拦截器添加到执行链的前端，将响应拦截器整个添加到执行链的后端
        Array.prototype.unshift.apply(chain, requestInterceptorChain);
        chain.concat(responseInterceptorChain);
        // 请求拦截器最先执行，可以拿到全部的config
        promise = Promise.resolve(config);
        while (chain.length) {
            // 两个为一组（正确和错误回调）逐个shift，解决回调地狱代码
            // 注意这里处理到dispatchRequest就会执行核心的异步请求，然后继续执行响应拦截器
            // 这里的参数也会利用promise逐层传递，由于链式调用的各个部分都是promise，相当于进行了链式调用
            // 注意在链式调用中，如果出错会执行对应的失败对调
            promise = promise.then(chain.shift(), chain.shift());
        }
        // 最终返回一个promise，这个相当于是经过处理后，我们最后使用axios时的异步函数，它的。then也就是我们最后使用的then
        return promise;
    }

    // 当拦截器为同步时，进行处理（如果是异步的话拦截器执行链已经执行完毕）
    var newConfig = config;
    // 首先处理请求拦截器
    while (requestInterceptorChain.length) {
        var onFulfilled = requestInterceptorChain.shift();
        var onRejected = requestInterceptorChain.shift();
        try {
            // 递归，每次调用成功的回调函数更新配置
            newConfig = onFulfilled(newConfig);
        } catch (error) {
            // 否则调用处理异常的函数
            onRejected(error);
            break;
        }
    }
    // 注意在早期版本中。dispatchRequest是放在Promise中进行处理的，相当于在微任务中执行请求
    // 由于微任务的创建是在promise链构建之前，因此这种方式会增加进行真正请求前的处理时间
    //注意到这里用try-catch微任务不会过早创建，也就解决了微任务过早创建，宏任务过长或异步阻塞导致请求延时处理的情况
    // 相当于之前是先创建微任务，再执行请求，
    // 现在将请求放入宏任务中，先执行请求再创建微任务，利用微任务创建时间和网络通信时间的重合，缩短响应时间
    try {
        promise = dispatchRequest(newConfig);
    } catch (error) {
        return Promise.reject(error);
    }
    // 用同样的方式处理响应拦截器,使其链式调用
    while (responseInterceptorChain.length) {
        promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift());
    }

    return promise;
};
// 获取完整url
Axios.prototype.getUri = function getUri(config) {
    config = mergeConfig(this.defaults, config);
    return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
};
// 使用工厂模式：批量处理和添加多种方法,分为需要携带数据和不需要携带数据的
// 这里的作用相当于兼容了更多的写法格式，比如axios.get('url'),axios.post("url",data)等，本质上还是调用request方法
// Provide aliases for supported request methods
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
    /*eslint func-names:0*/
    Axios.prototype[method] = function(url, config) {
        // 支持解构赋值
        return this.request(mergeConfig(config || {}, {
            method: method,
            url: url,
            data: (config || {}).data //优先使用config中的参数,也可以不携带
        }));
    };
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
    /*eslint func-names:0*/
    Axios.prototype[method] = function(url, data, config) {
        return this.request(mergeConfig(config || {}, {
            method: method,
            url: url,
            data: data //优先使用传进来的参数
        }));
    };
});

module.exports = Axios;